/*
 * Copyright 2013-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.springframework.cloud.gateway.handler;

import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.function.Function;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

/**
 * @author Spencer Gibb
 */
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {

	private final FilteringWebHandler webHandler;
	private final RouteLocator routeLocator;

	public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator) {
		this.webHandler = webHandler;
		this.routeLocator = routeLocator;
		
		// 调用setOrder(1)的原因，Spring Cloud Gateway的GatewayWebfluxEndpoint提供HTTP API，不需要经过网关，它通过RequestMappingHandlerMapping进行请求匹配处理。RequestMappingHandlerMapping的order=0 ，需要排在RoutePredicateHandlerMapping前面。所以RoutePredicateHandlerMapping设置order=1
		setOrder(1); // RequestMappingHandlerMapping之后
	}

	@Override
	// 匹配Route，并返回处理Route的FilteringWebHandler
	// getHandlerInternal()方法，在DispatcherHandler#handle(ServerWebExchange)方法被调用，匹配Route，并返回处理Route的FilteringWebHandler
	protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
	    // 设置 GATEWAY_HANDLER_MAPPER_ATTR 为 RoutePredicateHandlerMapping
		exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName());

		return lookupRoute(exchange) // 匹配 Route
				// .log("route-predicate-handler-mapping", Level.FINER) //name this
				.flatMap((Function<Route, Mono<?>>) r -> { // 返回 FilteringWebHandler
					if (logger.isDebugEnabled()) {
						logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
					}

					// 设置 GATEWAY_ROUTE_ATTR 为 匹配的 Route
					exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
					// 返回 FilteringWebHandler
					return Mono.just(webHandler);
				}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { // 匹配不到Route，返回 Mono.empty()，即不返回处理器
					if (logger.isTraceEnabled()) {
						logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
					}
				})));
	}

	//TODO: get desc from factory?
	private String getExchangeDesc(ServerWebExchange exchange) {
		StringBuilder out = new StringBuilder();
		out.append("Exchange: ");
		out.append(exchange.getRequest().getMethod());
		out.append(" ");
		out.append(exchange.getRequest().getURI());
		return out.toString();
	}

	protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
		// 调用RouteLocator#getRoutes()方法，获得全部Route
		return this.routeLocator.getRoutes()
				// 调用Predicate#test(ServerWebExchange)方法，顺序匹配一个Route
				.filter(route -> route.getPredicate().test(exchange))
				// .defaultIfEmpty() put a static Route not found
				// or .switchIfEmpty()
				// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
				.next()
				// 未来会增加匹配过程中发生异常的处理。目前，任何一个Predicate#test(ServerWebExchange)的方法调用发生异常时，都会导致匹配不到Route。一定要注意
				//TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("RouteDefinition matched: " + route.getId());
					}
					// 调用validateRoute(Route, ServerWebExchange)方法，校验Route的有效性。目前该方法是个空方法，可以通过继承RoutePredicateHandlerMapping进行覆盖重写
					validateRoute(route, exchange);
					return route;
				});

		/* TODO: trace logging
			if (logger.isTraceEnabled()) {
				logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
			}*/
	}

	/**
	 * Validate the given handler against the current request.
	 * <p>The default implementation is empty. Can be overridden in subclasses,
	 * for example to enforce specific preconditions expressed in URL mappings.
	 * @param route the Route object to validate
	 * @param exchange current exchange
	 * @throws Exception if validation failed
	 */
	@SuppressWarnings("UnusedParameters")
	protected void validateRoute(Route route, ServerWebExchange exchange) {
	}

}
